home *** CD-ROM | disk | FTP | other *** search
/ Language/OS - Multiplatform Resource Library / LANGUAGE OS.iso / t3_1 / encorsrc.lha / encore_sources / sys / pfloat.t < prev    next >
Text File  |  1988-05-02  |  17KB  |  399 lines

  1. (herald pfloat
  2.   (env tsys))
  3.  
  4. ;;; **********************************************************************
  5. ;;; This code was written as part of the Spice Lisp project at
  6. ;;; Carnegie-Mellon University, and has been placed in the public domain.
  7. ;;; Spice Lisp is currently incomplete and under active development.
  8. ;;; If you want to use this code or any part of Spice Lisp, please contact
  9. ;;; Scott Fahlman (FAHLMAN@CMUC). 
  10. ;;; **********************************************************************
  11.  
  12.  
  13. ;;;
  14. ;;; Spice Lisp printer.
  15. ;;; Written by Neal Feinberg, Spice Lisp Group.
  16. ;;; Currently maintained by Skef Wholey.
  17. ;;; 
  18. ;;; *******************************************************************
  19.  
  20. ;;; *** Adapted for use in T by J. Rees, Nov 1983 ***
  21.  
  22. ;;; *** Rewritten without SET by R. Kelsey, April 1986 ***
  23.  
  24. ;;;; Floating Point printing
  25. ;;;
  26. ;;;  Written by Bill Maddox
  27. ;;;
  28. ;;;
  29. ;;;
  30. ;;; FLONUM->STRING (and its subsidiary function FLOAT-STRING) does most of
  31. ;;; the work for all printing of floating point numbers in the printer and in
  32. ;;; FORMAT.  It converts a floating point number to a string in a free or 
  33. ;;; fixed format with no exponent.  The interpretation of the arguments is as 
  34. ;;; follows:
  35. ;;;
  36. ;;;     X        - The floating point number to convert, which must not be
  37. ;;;                negative.
  38. ;;;     WIDTH    - The preferred field width, used to determine the number
  39. ;;;                of fraction digits to produce if the FDIGITS parameter
  40. ;;;                is unspecified or NIL.  If the non-fraction digits and the
  41. ;;;                decimal point alone exceed this width, no fraction digits
  42. ;;;                will be produced unless a non-NIL value of FDIGITS has been
  43. ;;;                specified.  Field overflow is not considered an error at
  44. ;;;                this level.
  45. ;;;     FDIGITS  - The number of fractional digits to produce. Insignificant
  46. ;;;                trailing zeroes may be introduced as needed.  May be
  47. ;;;                unspecified or NIL, in which case as many digits as possible
  48. ;;;                are generated, subject to the constraint that there are no
  49. ;;;                trailing zeroes.
  50. ;;;     SCALE    - If this parameter is specified or non-NIL, then the number
  51. ;;;                printed is (* x (expt 10 scale)).  This scaling is exact,
  52. ;;;                and cannot lose precision.
  53. ;;;     FMIN     - This parameter, if specified or non-NIL, is the minimum
  54. ;;;                number of fraction digits which will be produced, regardless
  55. ;;;                of the value of WIDTH or FDIGITS.  This feature is used by
  56. ;;;                the ~E format directive to prevent complete loss of
  57. ;;;                significance in the printed value due to a bogus choice of
  58. ;;;                scale factor.
  59. ;;;
  60. ;;; Most of the optional arguments are for the benefit of FORMAT and are not
  61. ;;; used by the printer.
  62. ;;;
  63. ;;; Returns:
  64. ;;; (return DIGIT-STRING DIGIT-LENGTH LEADING-POINT TRAILING-POINT DECPNT)
  65. ;;; where the results have the following interpretation:
  66. ;;;
  67. ;;;     DIGIT-STRING    - The decimal representation of X, with decimal point.
  68. ;;;     DIGIT-LENGTH    - The length of the string DIGIT-STRING.
  69. ;;;     LEADING-POINT   - True if the first character of DIGIT-STRING is the
  70. ;;;                       decimal point.
  71. ;;;     TRAILING-POINT  - True if the last character of DIGIT-STRING is the
  72. ;;;                       decimal point.
  73. ;;;     POINT-POS       - The position of the digit preceding the decimal
  74. ;;;                       point.  Zero indicates point before first digit.
  75. ;;;
  76. ;;; NOTE:  FLONUM->STRING goes to a lot of trouble to guarantee accuracy.
  77. ;;; Specifically, the decimal number printed is the closest possible 
  78. ;;; approximation to the true value of the binary number to be printed from 
  79. ;;; among all decimal representations  with the same number of digits.  In
  80. ;;; free-format output, i.e. with the number of digits unconstrained, it is 
  81. ;;; guaranteed that all the information is preserved, so that a properly-
  82. ;;; rounding reader can reconstruct the original binary number, bit-for-bit, 
  83. ;;; from its printed decimal representation. Furthermore, only as many digits
  84. ;;; as necessary to satisfy this condition will be printed.
  85. ;;;
  86. ;;;
  87. ;;; FLOAT-STRING actually generates the digits for positive numbers.  The
  88. ;;; algorithm is essentially that of algorithm Dragon4 in "How to Print 
  89. ;;; Floating-Point Numbers Accurately" by Steele and White.  The current 
  90. ;;; (draft) version of this paper may be found in [CMUC]<steele>tradix.press.
  91. ;;; DO NOT EVEN THINK OF ATTEMPTING TO UNDERSTAND THIS CODE WITHOUT READING 
  92. ;;; THE PAPER!
  93.  
  94. ;;; E.g. (%ceiling 27 10) => 3
  95.  
  96. (define (%ceiling x y)          ; YUCKO !!
  97.   (receive (q r) (quotient&remainder x y)
  98.     (if (= r 0) q (+ q 1))))
  99.  
  100. ;;; Returns a STRING-BUFFER, which must be freed at some point.
  101.  
  102. (define (flonum->string x width fdigits scale fmin)
  103.   (cond ((zero? x)
  104.          ;; zero is a special case which float-string cannot handle
  105.          (let ((buffer (get-string-buffer)))
  106.            (string-writec buffer #\.)
  107.            (return buffer 1 t t 0)))
  108.         (else
  109.          (receive (#f mag exp)
  110.                   (integer-decode-float x)
  111.            (float-string mag exp (integer-length mag)
  112.                          width fdigits scale fmin)))))
  113.  
  114. (define (float-string fraction exponent precision width fdigits scale fmin)
  115.   (receive (r s m- m+ log-r roundup? cutoff)
  116.            (rationalize-float fraction exponent precision
  117.                               width fdigits scale fmin)
  118.     (let ((buffer (get-string-buffer)))
  119.       (write-preceding-zeros log-r buffer)
  120.       (write-digits r s m- m+ log-r buffer cutoff roundup?)
  121.       (let ((decpnt (string-posq #\. buffer)))
  122.         ;;add trailing zeroes to pad fraction if fdigits specified
  123.         (if fdigits
  124.             (add-asked-for-zeroes fdigits buffer decpnt))
  125.         (let ((digits (string-buffer-length buffer)))
  126.           (return buffer
  127.                   digits
  128.                   (fx= decpnt 0)
  129.                   (fx= decpnt (fx- digits 1))
  130.                   decpnt))))))
  131.  
  132. (define  (add-asked-for-zeroes fdigits buffer decpnt)
  133.   (let ((needed-zeros (fx- fdigits (fx- (string-buffer-length buffer)
  134.                                         decpnt))))
  135.     (do ((i needed-zeros (fx- i 1)))
  136.         ((fx<= i -1)) ; off by one?
  137.       (string-writec buffer #\0))))
  138.         
  139. ;; Represent fraction as r/s, error bounds as m+/s and m-/s.
  140. ;; Rational arithmetic avoids loss of precision in subsequent calculations.
  141.  
  142. (define (rationalize-float fraction exponent precision width fdigits scale fmin)
  143.   (receive (r s m- m+)
  144.            (really-rationalize-float fraction exponent precision)
  145.     (receive (r s m- m+)
  146.              (scale-up r s m- m+ scale)
  147.       (receive (r s m- m+ log-r)
  148.                (compute-log-r r s m- m+)
  149.         (iterate loop ((r r) (s s) (m- m-) (m+ m+) (log-r log-r) (roundup? nil)) 
  150.           (receive (r s m- m+ log-r)
  151.                    (recompute-log-r r s m- m+ log-r)
  152.             (let ((cutoff (get-digit-cutoff width fdigits fmin log-r)))
  153.               (receive (m- m+ new-ru?)
  154.                        (if cutoff
  155.                            (adjust-error m- m+ s (fx- cutoff log-r))
  156.                            (return m- m+ nil))
  157.                 (if (< (+ (%ash r 1) m+) (%ash s 1))
  158.                     (return r s m- m+ log-r (or roundup? new-ru?) cutoff)
  159.                     (loop r s m- m+ log-r (or roundup? new-ru?)))))))))))
  160.  
  161. (define (really-rationalize-float fraction exponent precision)
  162.   (receive (r s m- m+)
  163.            (cond ((fx> exponent 0)
  164.                   (return (%ash fraction exponent)
  165.                           1
  166.                           (%ash 1 exponent)
  167.                           (%ash 1 exponent))) 
  168.                  ((fx< exponent 0)
  169.                   (return fraction (%ash 1 (fx- 0 exponent)) 1 1))
  170.                  (else
  171.                   (return fraction 1 1 1)))
  172.     ;; adjust the error bounds m+ and m- for unequal gaps
  173.     (if (= fraction (%ash 1 precision))
  174.         (return (%ash r 1) (%ash s 1) m- (%ash m+ 1))
  175.         (return r s m+ m-))))
  176.  
  177. ;; scale value by requested amount, and update error bounds
  178.  
  179. (define (scale-up r s m- m+ scale)
  180.   (cond ((not scale)
  181.          (return r s m- m+))
  182.         ((fx< scale 0)
  183.          (let ((scale-factor (expt 10 (fx- 0 scale))))
  184.            (return r (* s scale-factor) m- m+)))
  185.         (else
  186.          (let ((scale-factor (expt 10 scale)))
  187.            (return (* r scale-factor)
  188.                    s
  189.                    (* m- scale-factor)
  190.                    (* m+ scale-factor))))))
  191.  
  192. (define (compute-log-r r s m- m+)
  193.   (let ((c (%ceiling s 10)))
  194.     (do ((k 0 (fx- k 1))
  195.          (d 1 (* d 10)))
  196.         ((>= (* r d) c)
  197.          (return (* r d) s (* m- d) (* m+ d) k)))))
  198.  
  199. (define (recompute-log-r r s m- m+ log-r)
  200.   (let ((z (+ (%ash r 1) m+)))
  201.     (do ((s s (* s 10))
  202.          (k log-r (fx+ k 1)))
  203.         ((< z (%ash s 1))
  204.          (return r s m- m+ k)))))
  205.  
  206. ;;determine number of fraction digits to generate
  207.  
  208. (define (get-digit-cutoff width fdigits fmin log-r)
  209.   ;;don't allow less than fmin fraction digits
  210.   (let ((fix-fmin (lambda (maybe)
  211.                     (if (and fmin (fx> maybe (fx- 0 fmin)))
  212.                         (fx- 0 fmin)
  213.                     maybe))))
  214.     (cond (fdigits
  215.            ;;use specified number of fraction digits
  216.            (fix-fmin (fx- 0 fdigits)))
  217.           ((not width)
  218.            nil)
  219.           ((fx< log-r 0)
  220.            ;;use as many fraction digits as width will permit
  221.            (fix-fmin (fx- 1 width)))
  222.           (else
  223.            (fix-fmin (fx+ 1 (fx- log-r width)))))))
  224.  
  225. ;;If we decided to cut off digit generation before precision has
  226. ;;been exhausted, rounding the last digit may cause a carry propagation.
  227. ;;We can prevent this, preserving left-to-right digit generation, with
  228. ;;a few magical adjustments to m- and m+.  Of course, correct rounding
  229. ;;is also preserved.
  230.  
  231. (define (adjust-error m- m+ s count)
  232.   (let ((y (if (fx>= count 0)
  233.                (* s (expt 10 count))
  234.                (do ((i count (fx+ i 1))
  235.                     (y s (%ceiling y 10)))
  236.                    ((fx>= i 0) y)))))
  237.     (return (max y m-) (max y m+) (>= y m+))))
  238.  
  239. ;;zero-fill before fraction if no integer part
  240.  
  241. (define (write-preceding-zeros log-r buffer)
  242.   (cond ((fx>= log-r 0)
  243.          0)
  244.         (else
  245.          (string-writec buffer #\.)
  246.          (do ((i log-r (fx+ i 1)))
  247.              ((fx>= i 0)
  248.               (fx- 0 log-r))
  249.            (string-writec buffer #\0)))))
  250.  
  251. ;;generate the significant digits
  252.  
  253. (define (write-digits r s m- m+ log-r buffer cutoff roundup?)
  254.   (let ((ash-s-1 (%ash s 1)))
  255.     (iterate loop ((r r) (m- (* m- 10)) (m+ (* m+ 10)) (log-r (fx- log-r 1)))
  256.       (if (fx= log-r -1) (string-writec buffer #\.))
  257.       ;;(multiple-value-set (u r) (truncate (* r 10) s))
  258.       (receive (u r)
  259.                (quotient&remainder (* r 10) s)
  260.         (let* ((ash-r-1 (%ash r 1))
  261.                (low (< ash-r-1 m-))
  262.                (high (if roundup?
  263.                          (>= ash-r-1 (- ash-s-1 m+))
  264.                          (>  ash-r-1 (- ash-s-1 m+)))))
  265.       ;;stop when either precision is exhausted or we have printed as many
  266.       ;;fraction digits as permitted
  267.           (cond ((or low high (and cutoff (fx<= log-r cutoff)))
  268.                  ;;if cutoff occured before first digit, then no digits generated
  269.                  ;;at all.  last digit may need rounding
  270.                  (if (or (not cutoff) (fx>= log-r cutoff))
  271.                      (string-writec buffer (last-digit u high low r s)))
  272.                  ;;zero-fill after integer part if no fraction
  273.                  (if (fx>= log-r 0)
  274.                      (add-decimal-point log-r buffer)))
  275.                 (else
  276.                  (string-writec buffer (digit->char u 10))
  277.                  (loop r (* m- 10) (* m+ 10) (fx- log-r 1)))))))))
  278.  
  279. (define (last-digit u high low r s)
  280.   (digit->char (fx+ u (cond ((and low (not high)) 0)
  281.                             ((and high (not low)) 1)
  282.                             ((<= (%ash r 1) s)    0)
  283.                             (else                 1)))
  284.                10))
  285.  
  286. (define (add-decimal-point log-r buffer)
  287.   (do ((i log-r (fx- i 1)))
  288.       ((fx<= i 0))
  289.     (string-writec buffer #\0))
  290.   (string-writec buffer #\.))
  291.  
  292.  
  293. ;;; Given a non-negative floating point number, SCALE-EXPONENT returns a
  294. ;;; new floating point number Z in the range (0.1, 1.0] and an exponent
  295. ;;; E such that Z * 10^E is (approximately) equal to the original number.
  296. ;;; There may be some loss of precision due the floating point representation.
  297.  
  298. ;(defconstant short-log10-of-2 #~F0.30103s0)
  299. (define-constant %sp-l-float fixnum->flonum)
  300. (define-constant %long-float-ten (fixnum->flonum 10))
  301. (define-constant %long-float-one-tenth (/ 1 %long-float-ten))
  302. (define (log10 x) (fl/ (log x) (log %long-float-ten)))
  303. (define-constant long-log10-of-2 (log10 (fixnum->flonum 2)))
  304. (define-constant zero (fixnum->flonum 0))
  305.  
  306. (define (scale-exponent x)
  307.       (scale-expt-aux x (%sp-l-float 0) (%sp-l-float 1) %long-float-ten
  308.                       %long-float-one-tenth long-log10-of-2))
  309.  
  310.  
  311. (define (scale-expt-aux x zero one ten one-tenth log10-of-2)
  312.   (receive (#f #f exponent)
  313.            (integer-decode-float x)
  314.     (if (fl= x zero)
  315.         (return zero 1)
  316.         (let* ((e (flonum->fixnum (fl* (fixnum->flonum exponent) log10-of-2)))
  317.                (x (if (fx< e 0)                ;For the end ranges.
  318.                       (* (* x ten) (expt ten (fx- -1 e)))
  319.                       (/ (/ x ten) (expt ten (fx-  e 1))))))
  320.           (do ((d ten (* d ten))
  321.                (y x (/ x d))
  322.                (e e (fx+ 1 e)))
  323.               ((< y one)
  324.                (do ((m ten (* m ten))
  325.                     (z y (* z m))
  326.                     (e e (fx- e 1)))
  327.                    ((>= z one-tenth) (return z e)))))))))
  328.  
  329. ;;; Entry point for the float printer as called by PRINT, PRIN1, PRINC,
  330. ;;; etc.  The argument is printed free-format, in either exponential or 
  331. ;;; non-exponential notation, depending on its magnitude.
  332. ;;;
  333. ;;; NOTE:  When a number is to be printed in exponential format, it is scaled
  334. ;;; in floating point.  Since precision may be lost in this process, the
  335. ;;; guaranteed accuracy properties of FLONUM->STRING are lost.  The
  336. ;;; difficulty is that FLONUM->STRING performs extensive computations with
  337. ;;; integers of similar magnitude to that of the number being printed.  For
  338. ;;; large exponents, the bignums really get out of hand.  When we switch to
  339. ;;; IEEE format for long floats, this will significantly restrict the magnitude
  340. ;;; of the largest allowable float.  This combined with microcoded bignum
  341. ;;; arithmetic might make it attractive to handle exponential notation with
  342. ;;; the same accuracy as non-exponential notation, using the method described
  343. ;;; in the Steele and White paper.
  344.  
  345. (define %long-float1l-3 (/ (fixnum->flonum 1) 1000)) ;1.0e-3
  346. (define %long-float1l7  (fixnum->flonum 10000000))   ;1.0e7
  347.  
  348. (define (print-flonum x stream)         ;Entry point from PRINT
  349.   (output-float-aux x %long-float1l-3 %long-float1l7 stream))
  350.  
  351. ;;; There is (was?) a TC bug lurking in here.  Beware.
  352.  
  353. (define (output-float-aux x e-min e-max stream)
  354.   (cond ((nan? x) (write-string stream "NaN"))
  355.     ((fl= x 0.0) (write-string stream "0.0")
  356.                    ;(if (not (typep x *read-default-float-format*))
  357.                    ;    (write-string (if (typep x 'short-float) "s0" "L0")))
  358.                    )
  359.         (else
  360.          (let ((x (cond ((fl< x 0.0)
  361.                          (write-char stream #\-)
  362.                          (fl- 0.0 x))
  363.                         (else x))))
  364.            (if (and (fl>= x e-min) (fl< x e-max))
  365.                (output-free-float x stream)
  366.                (output-exponential-float x stream))))))
  367.  
  368. (define (output-free-float x stream)
  369.   (receive (str len lpoint tpoint ())
  370.            (flonum->string x nil nil nil nil)
  371.     (if lpoint (write-char stream #\0))
  372.     (write-string stream str)
  373.     (release-string-buffer str)
  374.     (if tpoint (write-char stream #\0))
  375.     ;(if (not (typep x *read-default-float-format*))
  376.     ;    (write-string (if (typep x 'short-float) "s0" "L0")))
  377.     ))
  378.  
  379. (define (output-exponential-float x stream)
  380.   (receive (f e)
  381.            (scale-exponent x)
  382.     (receive (str len lpoint tpoint ())
  383.              (flonum->string f nil nil 1 nil)
  384.       (if lpoint (write-char stream #\0))
  385.       (write-string stream str)
  386.       (release-string-buffer str)
  387.       (if tpoint (write-char stream #\0))
  388.       (write-char stream
  389.                   ;(if (typep x *read-default-float-format*)
  390.                       #\E
  391.                       ;(if (typep x 'short-float) #\S #\L))
  392.       )
  393.       ;; must subtract 1 from exponent here, due to
  394.       ;; the scale factor of 1 in call to FLONUM->STRING
  395.       (if (not (fx< (fx- e 1) 0)) (write-char stream #\+))
  396.       (print (fx- e 1) stream))))
  397.  
  398. (set *print-flonums-kludgily?* nil)
  399.